<?php
/**
* Classe champ.
*
* PHP version 5.1.6
*
* @package   Smartbok
* @author    Christophe Hebet <christophe.hebet@dgfip.finances.gouv.fr>
* @copyright 2013 - DGFiP
*/

class SmartbokChamp
{
    const SBK_TYPE_TEXTE = 10;
    const SBK_TYPE_MAIL = 11;
    const SBK_TYPE_DATE = 20;
    const SBK_TYPE_ENTIER = 30;
    const SBK_TYPE_NOMBRE = 31;

    const SBK_TEXTE_CONTROLE_LONGUEUR = 10;
    const SBK_TEXTE_CONTROLE_REGEXP = 11;

    const SBK_MAIL_CONTROLE = 12;

    const SBK_DATE_CONTROLE = 20;
    const SBK_DATE_CONTROLE_INF = 21;
    const SBK_DATE_CONTROLE_SUP = 22;
    const SBK_DATE_CONTROLE_BORNES_IN = 23;
    const SBK_DATE_CONTROLE_BORNES_OUT = 24;

    const SBK_ENTIER_CONTROLE = 30;
    const SBK_NOMBRE_CONTROLE = 3;

    const SBK_NOMBRE_CONTROLE_POSITIF = 40;
    const SBK_NOMBRE_CONTROLE_NEGATIF = 41;
    const SBK_NOMBRE_CONTROLE_SUP = 42;
    const SBK_NOMBRE_CONTROLE_INF = 43;
    const SBK_NOMBRE_CONTROLE_BORNES_IN = 44;
    const SBK_NOMBRE_CONTROLE_BORNES_OUT = 45;
    const SBK_NOMBRE_CONTROLE_VIRGULE = 46;

    private $_type = null;
    private $_id = null;
    private $_name = null;
    private $_libelle = null;
    private $_obligatoire = false;
    private $_valeur = null;
    private $_controles = null;
    private $_listeValeursAutorisees = null;
    private $_controlesAutorises = array(self::SBK_TYPE_TEXTE => array(self::SBK_TEXTE_CONTROLE_LONGUEUR,
                                                                       self::SBK_TEXTE_CONTROLE_REGEXP),
                                         self::SBK_TYPE_MAIL => array(self::SBK_TEXTE_CONTROLE_LONGUEUR,
                                                                      self::SBK_TEXTE_CONTROLE_REGEXP),
                                         self::SBK_TYPE_DATE => array(self::SBK_DATE_CONTROLE,
                                                                      self::SBK_DATE_CONTROLE_INF,
                                                                      self::SBK_DATE_CONTROLE_SUP,
                                                                      self::SBK_DATE_CONTROLE_BORNES_IN,
                                                                      self::SBK_DATE_CONTROLE_BORNES_OUT),
                                         self::SBK_TYPE_ENTIER => array(self::SBK_ENTIER_CONTROLE,
                                                                        self::SBK_NOMBRE_CONTROLE_POSITIF,
                                                                        self::SBK_NOMBRE_CONTROLE_NEGATIF,
                                                                        self::SBK_NOMBRE_CONTROLE_SUP,
                                                                        self::SBK_NOMBRE_CONTROLE_INF,
                                                                        self::SBK_NOMBRE_CONTROLE_BORNES_IN,
                                                                        self::SBK_NOMBRE_CONTROLE_BORNES_OUT),

                                         self::SBK_TYPE_NOMBRE => array(self::SBK_NOMBRE_CONTROLE,
                                                                        self::SBK_NOMBRE_CONTROLE_POSITIF,
                                                                        self::SBK_NOMBRE_CONTROLE_NEGATIF,
                                                                        self::SBK_NOMBRE_CONTROLE_SUP,
                                                                        self::SBK_NOMBRE_CONTROLE_INF,
                                                                        self::SBK_NOMBRE_CONTROLE_BORNES_IN,
                                                                        self::SBK_NOMBRE_CONTROLE_BORNES_OUT,
                                                                        self::SBK_NOMBRE_CONTROLE_VIRGULE)
                                          );
    private $_messagesErreur = array(self::SBK_TEXTE_CONTROLE_LONGUEUR => 'Le champ #NOMCHAMP# dépasse la longueur autorisée(#PARAM#).',
                                    self::SBK_TEXTE_CONTROLE_REGEXP => 'Le champ #NOMCHAMP# ne respecte pas le format attendu.',
                                    self::SBK_DATE_CONTROLE => 'Le champ #NOMCHAMP# contient une date invalide.',
                                    self::SBK_DATE_CONTROLE_INF => 'Le champ #NOMCHAMP# contient une date supérieure à la borne fixée(#PARAM#).',
                                    self::SBK_DATE_CONTROLE_SUP => 'Le champ #NOMCHAMP# contient une date inférieure à la borne fixée(#PARAM#).',
                                    self::SBK_DATE_CONTROLE_BORNES_IN => 'Le champ #NOMCHAMP# contient une date en dehors des bornes fixées(#PARAM#).',
                                    self::SBK_DATE_CONTROLE_BORNES_OUT => 'Le champ #NOMCHAMP# contient une date à l\'intérieur des bornes fixées(#PARAM#).',
                                    self::SBK_ENTIER_CONTROLE => 'La valeur saisie dans Le champ #NOMCHAMP# n\'est pas un entier.',
                                    self::SBK_NOMBRE_CONTROLE_POSITIF => 'Le champ #NOMCHAMP# contient un nombre non positif.',
                                    self::SBK_NOMBRE_CONTROLE_NEGATIF => 'Le champ #NOMCHAMP# contient un nombre non négatif.',
                                    self::SBK_NOMBRE_CONTROLE_SUP => 'Le champ #NOMCHAMP# contient un nombre inférieur à la borne (#PARAM#).',
                                    self::SBK_NOMBRE_CONTROLE_INF => 'Le champ #NOMCHAMP# contient un nombre supérieur à la borne (#PARAM#).',
                                    self::SBK_NOMBRE_CONTROLE_BORNES_IN => 'Le champ #NOMCHAMP# contient un nombre en dehors des bormes définies(#PARAM#).',
                                    self::SBK_NOMBRE_CONTROLE_BORNES_OUT => 'Le champ #NOMCHAMP# contient un nombre à l\'intérieur des bormes définies(#PARAM#).',
                                    self::SBK_NOMBRE_CONTROLE => 'La valeur saisie dans Le champ #NOMCHAMP# n\'est pas un nombre.',
                                    self::SBK_NOMBRE_CONTROLE_VIRGULE => 'Le champ #NOMCHAMP# contient un nombre dont la valeur décimale dépasse la longueur fixée(#PARAM#).'
                                    );
    private $_controlesOk = true;
    private $_controlesApplicatifs = null;

    /**
     * constructeur de la classe
     * le paramètre est une  array contenant les params  :
     *              soit dans l'ordre (type, nom, libelle, obligatoire, valeur, controles),
     *              soit une array indicée sur les nom de paramètres)
     *
     * @param string|array $params
     */
    public function __construct($params) {
        if (key_exists('_type', $params)) {
            foreach ($params as $key => $param) {
                $this->$key = $param;
            }
        }
        else {
            $classVars = array_keys(get_class_vars(get_class($this)));
            $v = current($classVars);
            foreach ($params as $param) {
                $this->$v = $param;
                $v = next($classVars);
            }
        }
    }

    public function modifierChamp($attr, $valeur) {
        $this->$attr = $valeur;
    }

    public function getValue($nomAttribut) {
        return $this->$nomAttribut;
    }

    /**
     * Fonction d'insertion du controle à l'objet. Le contrôle n'est ajouté que si le contrôle fait partie des
     * contrôle autorisés pour le type de champ
     * @param integer $controle
     * @param string|array $params
     */
    public function ajouterControle($controle, $params= null) {
        if (in_array($controle, $this->_controlesAutorises[$this->_type]) === true) {
            $this->_controles[$controle] = $params;
        }
    }

    /**
     * ajoute un controle applicatif au champ
     *
     * @param string $controle
     * @param string|array $params
     * @param string $object
     */
    public function ajouterControleApplicatif($controle, $params= null, &$objet= null) {
        $this->_controlesApplicatifs[] = array($controle, $params, $objet);
    }

    public function controler() {
        if ($this->_obligatoire === true && strlen($this->_valeur) === 0) {
           $msgObligatoire = 'La saisie du champ <span>'.$this->_libelle.'</span> est obligatoire.';
           SmartbokMessage::addMessage(SmartbokMessage::ERR, $msgObligatoire);
           $this->_controlesOk = false;
           return false;
        }
        else {
            if(strlen($this->_valeur) >0 && $retour = $this->_controleValeurAutorisee() === true) {
	            switch ($this->_type) {
	                case self::SBK_TYPE_TEXTE:
	                case self::SBK_TYPE_MAIL;
	                    $retour = $this->_controlerTexte();
	                break;

	                case self::SBK_TYPE_NOMBRE:
	                case self::SBK_TYPE_ENTIER:
	                    $retour = $this->_controlerNombre();
	                break;

	                case self::SBK_TYPE_DATE:
	                    $retour = $this->_controlerDate();
	                break;

	                default:
	                  // pas de cas par défaut
	                break;
 	            }
 	            $retour = $this->_executerControleApplicatif($retour);
            }
            else {
                $retour = true;
            }
        }
        $this->_controlesOk = $retour;
        return $retour;
    }

    private function _executerControleApplicatif($retour) {
        if (isset($this->_controlesApplicatifs) === true) {
            foreach ($this->_controlesApplicatifs as $controle) {
                $params = null;
                // vérifie si des paramètres ont été passé en arguement du controle
                if (isset($controle[1]) === true) {
                    $params = $controle[1];
                }
                if (isset($controle[2]) === true) {
                    $retour =  $controle[2]->$controle[0]($params);
                }
                else if (function_exists($controle[0]) === true) {
                    $retour = $controle[0]($params);
                }
            }
        }
        return $retour;
    }

    private function _controleValeurAutorisee() {
        if ($this->_listeValeursAutorisees !== null) {
            if (in_array($this->_valeur, $this->_listeValeursAutorisees) === false) {
            $msgValeurNonAutorise = 'Le champ '.$this->_libelle.' contient une valeur non autorisée.';
            SmartbokMessage::addMessage(SmartbokMessage::ERR, $msgValeurNonAutorise);
            return false;
            }
        }
        return true;
    }

    public function ajouterListeValeurAutorisees($liste) {
        if (is_array($liste) === false) {
            $liste = array($liste);
        }
        if ($this->_listeValeursAutorisees === null) {
            $this->_listeValeursAutorisees = $liste;
        }
        else {
            $this->_listeValeursAutorisees = array_merge($this->_listeValeursAutorisees, $liste);
        }
    }
    /**
     * Méthode privée du contrôle des champs texte. vérifie la longueur ou le type (mail) et le resepct éventuel
     * d'une expression régulière
     *
     * @return boolean
     */
    private function _controlerTexte() {
        $retour = true;
        if ($this->_type === self::SBK_TYPE_MAIL) {
            // pattern issu de la norme RFC5322
            $pattern = '/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:';
            $pattern .= '\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27';
            $pattern .= '\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-';
            $pattern .= '\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-';
            $pattern .= '\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C';
            $pattern .= '\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]';
            $pattern .= '{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:';
            $pattern .= '(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4})';
            $pattern .= '{7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]';
            $pattern .= '{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|';
            $pattern .= '(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::';
            $pattern .= '[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.';
            $pattern .= '(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD';

            $nbOccurence = preg_match($pattern, $this->_valeur);
            if ($nbOccurence === 0) {
                $msgMail = 'Le champ '.$this->_libelle.' contient une adresse mail invalide.';
                SmartbokMessage::addMessage(SmartbokMessage::ERR, $msgMail);
                $retour = false;
            }
        }
        if ($this->_controles !== null) {
            foreach ($this->_controles as $type => $params) {
                if (is_array($params) === true) {
                    $param = $params[0];
                }
                else {
                    $param = $params;
                }
                switch ($type) {
                    case self::SBK_TEXTE_CONTROLE_LONGUEUR:
                        if (strlen($this->_valeur) > $param) {
                            $retour = false;
                            $this->_ajouterMessage($type, $param);
                        }
                    break;

                    case self::SBK_TEXTE_CONTROLE_REGEXP:
                        $nbOccurence = preg_match($param, $this->_valeur);
                        if ($nbOccurence === 0) {
                            $this->_ajouterMessage($type);
                            $retour = false;
                        }
                    break;

                    default:
                        // pas de traitement par défaut ;
                    break;
                }
            }
        }
        return $retour;
    }

    private function _ajouterMessage($type, $param=null) {
        $msg = $this->_messagesErreur[$type];
        $msg = str_replace('#NOMCHAMP#', '<span>'.$this->_libelle.'</span>', $msg);
        $msg = str_replace('#VALEUR#', '<span>'.$this->_valeur.'</span>', $msg);
        $msg = str_replace('#PARAM#', '<span>'.$param.'</span>', $msg);
        SmartbokMessage::addMessage(SmartbokMessage::ERR, $msg);
    }

    public function modifierMessageErreur($type, $msg) {
        $this->_messagesErreur[$type] = $msg;
    }
    /**
     * Méthode privée des contrôle de dates. vérification de l'élligibilité de la date, et des éventuelles
     * bornes a respecter
     *
     * @return boolean
     */
    private function _controlerDate() {
        $retour = true;
        if ($this->_controles !== null) {
            $date = $this->_checkDate($this->_valeur);
            if ($date === false) {
                return false;
            }
            $tabDate = explode('/', $this->_valeur);
            $date = mktime(0, 0, 0, $tabDate[1], $tabDate[0], $tabDate[2]);
            foreach ($this->_controles as $type => $params) {
                switch ($type) {

                    case self::SBK_DATE_CONTROLE_INF:
                    case self::SBK_DATE_CONTROLE_SUP:
                        if (is_array($params)) {
                            $params = $params[0];
                        }
                        $tabD1 = explode('/', $params);

                        $d1 = mktime(0, 0, 0, $tabD1[1], $tabD1[0], $tabD1[2]);
                        if (($date > $d1 && $type === self::SBK_DATE_CONTROLE_INF) ||
                            ($date < $d1 && $type === self::SBK_DATE_CONTROLE_SUP)){
                            $retour = false;
                            $this->_ajouterMessage($type, $params);
                        }
                    break;

                    case self::SBK_DATE_CONTROLE_BORNES_IN:
                    case self::SBK_DATE_CONTROLE_BORNES_OUT:
                        $tabD1 = explode('/', $params[0]);
                        $d1 = mktime(0, 0, 0, $tabD1[1], $tabD1[0], $tabD1[2]);
                        $tabD2 = explode('/', $params[1]);
                        $d2 = mktime(0, 0, 0, $tabD2[1], $tabD2[0], $tabD2[2]);
                        $params = $params[0].', '.$params[1];
                        if ((($date < $d1  || $date > $d2) && $type === self::SBK_DATE_CONTROLE_BORNES_IN) ||
                            (($date > $d1  && $date < $d2) && $type === self::SBK_DATE_CONTROLE_BORNES_OUT)) {
                            $retour = false;
                            $this->_ajouterMessage($type, $params);
                        }
                    break;
                    default:
                        // pas de traitement par défaut ;
                        break;
                }
            }
        }
        else if ($this->_valeur !== null){
            $retour = $this->_checkDate($this->_valeur);
        }
        return $retour;
    }

    private function _checkDate($valeur) {
        $tabDate = explode('/', $valeur);
        foreach ($tabDate as $dateElement) {
            if (ctype_digit(strval($dateElement)) === false) {
                $this->_ajouterMessage(self::SBK_DATE_CONTROLE);
                return false;
            }
        }
        $date = checkdate($tabDate[1], $tabDate[0], $tabDate[2]);
        if ($date === false) {
                $this->_ajouterMessage(self::SBK_DATE_CONTROLE);
                return false;
        }
        return true;
    }
    /**
     * Méthode privée de contrôle des nombres a partir d'une expression régulière, entier, décimal, nombre
     * après la virgule
     *
     * @return boolean
     */
    private function _controlerNombre() {
        $retour = true;
        if ($this->_controles !== null) {
            $nombre = $this->_checkNombre($this->_type, $this->_valeur);
            if ($nombre === false) {
                return false;
            }
            $valeur = str_replace(',', '.', $this->_valeur);
            foreach ($this->_controles as $type => $params) {
                switch ($type) {

                    case self::SBK_NOMBRE_CONTROLE_POSITIF:
                        $params = 0;
                    case self::SBK_NOMBRE_CONTROLE_SUP:
                        if (floatval($valeur) < $params) {
                            $retour = false;
                            $this->_ajouterMessage($type, $params);
                        }
                    break;

                    case self::SBK_NOMBRE_CONTROLE_NEGATIF:
                        $params = 0;
                    case self::SBK_NOMBRE_CONTROLE_INF:
                        if (floatval($valeur) > $params) {
                            $retour = false;
                            $this->_ajouterMessage($type, $params);
                        }
                    break;

                    case self::SBK_NOMBRE_CONTROLE_BORNES_IN:
                    case self::SBK_NOMBRE_CONTROLE_BORNES_OUT:
                        if ((($valeur < $params[0] || $valeur > $params[1]) &&
                                        $type === self::SBK_NOMBRE_CONTROLE_BORNES_IN) ||
                           (($valeur > $params[0]  && $valeur < $params[1]) &&
                                        $type === self::SBK_NOMBRE_CONTROLE_BORNES_OUT)) {
                            $retour = false;
                            $this->_ajouterMessage($type, $params[0].','.$params[1]);
                        }
                    break;

                    case self::SBK_NOMBRE_CONTROLE_VIRGULE :
                        $virg = substr($valeur, strpos($valeur, '.')+1);
                        if (strlen($virg) > $params) {
                            $retour = false;
                            $this->_ajouterMessage($type, $params);
                        }
                    break;

                    default:
                        // pas de traitement par défaut ;
                        break;
                }
            }
        }
        else if ($this->_valeur !== null){
            $retour = $this->_checkNombre($this->_type, $this->_valeur);
        }
    	return $retour;
    }

    private function _checkNombre($type, $valeur) {
        $retour = true;
        if (trim(substr($valeur, 0, 1)) === '-') {
            $valeur = substr($valeur, 1);
        }
        if ($type === self::SBK_TYPE_NOMBRE) {
            $valeur = str_replace(array(',', '.'), '0', $valeur);
            if (ctype_digit(strval($valeur)) !== true) {
                $this->_ajouterMessage(self::SBK_NOMBRE_CONTROLE);
                return false;
            }
        }
        if ($type === self::SBK_TYPE_ENTIER) {
           if (ctype_digit(strval($valeur)) !== true) {
               $this->_ajouterMessage(self::SBK_ENTIER_CONTROLE);
               return false;
           }
        }
        return $retour;
    }
}